package org.gap.jseed.annotation;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import javax.swing.event.EventListenerList;

import org.gap.jseed.AbstractClassInvocationHandler;
import org.gap.jseed.util.EqualsUtil;

public class BeanInvocationHandler extends AbstractClassInvocationHandler implements Serializable {
	private static final long serialVersionUID = 49755597995027665L;
	private Map<String, Object> properties;
	private EventListenerList eventListenerList;
	
	public BeanInvocationHandler() {
		properties = new HashMap<String, Object>();
		eventListenerList = new EventListenerList();
	}
	
	@Override
	public Object invokeMethod(Object proxy, Method method, Object[] args) {
		Object result = null;
		
		String name = method.getName();
		if (name.startsWith("set") && oneArgs(args)) {
			Object oldValue = properties.get(BeanUtil.getPropertyName(name));			
			properties.put(BeanUtil.getPropertyName(name), args[0]);
			if (EqualsUtil.notEquals(oldValue, args[0])) {
				fireChangeEvent(proxy, BeanUtil.getPropertyName(method.getName()), oldValue, args[0]);
			}
		} else if (name.startsWith("get") && noArgs(args) && !isCollection(method)) {
			result = properties.get(BeanUtil.getPropertyName(name));
		} else if (name.startsWith("is") && noArgs(args)) {
			result = properties.get(BeanUtil.getPropertyName(name));
		} else if (name.startsWith("add") && name.endsWith("Listener") && oneArgs(args)) {
			if (args[0] instanceof PropertyChangeListener) {
				eventListenerList.add(PropertyChangeListener.class, (PropertyChangeListener)args[0]);
			}
		} else if (name.startsWith("remove") && name.endsWith("Listener") && oneArgs(args)) {
			if (args[0] instanceof PropertyChangeListener) {
				eventListenerList.remove(PropertyChangeListener.class, (PropertyChangeListener)args[0]);
			}
		} else if (name.startsWith("add") && oneArgs(args)) {
			String property = BeanUtil.getPropertyName(method.getName()) + "s";
			ensureCollection(property).add(args[0]);
			fireChangeEvent(proxy, property, null, args[0]);
		} else if (name.startsWith("remove") && oneArgs(args)) {
			String property = BeanUtil.getPropertyName(method.getName()) + "s";
			ensureCollection(property).remove(args[0]);
			fireChangeEvent(proxy, property, null, args[0]);
		} else if (name.startsWith("getAll") && isCollection(method)) {
			String property = BeanUtil.getPropertyName(method.getName());
			result = ensureCollection(property);
		}
		
		return result;
	}

	private boolean isCollection(Method method) {
		Class<?> returnType = method.getReturnType();
		return returnType.isAssignableFrom(java.util.Collection.class);
	}

	@SuppressWarnings("unchecked")
	private java.util.LinkedList<Object> ensureCollection(String property) {
		if (!properties.containsKey(property)) {
			properties.put(property, new java.util.LinkedList<Object>());
		}
		return (java.util.LinkedList<Object>)properties.get(property);
	}

	@Override
	protected boolean invokeEquals(InvocationHandler proxy, InvocationHandler other) {
		BeanInvocationHandler otherBean = (BeanInvocationHandler)other;
		if (otherBean.properties.size() != properties.size()) {
			return false;
		} else {
			for (String each : properties.keySet()) {
				Object value = otherBean.properties.get(each);
				if (value.equals(properties.get(each))) {
					continue;
				} else {
					return false;
				}
			}
		}
		return true;
	}

	@Override
	protected int invokeHashCode(Object proxy) {
		int value = proxy.getClass().hashCode();
		for (String each : properties.keySet()) {
			value += properties.get(each).hashCode();
		}
		return value;
	}

	@Override
	protected String invokeToString(Object proxy) {
		StringBuffer buffer = new StringBuffer(proxy.getClass().getName() + " [");
		for (String each : properties.keySet()) {
			buffer.append("\t" + each + ": " + properties.get(each));
		}
		buffer.append("]");
		return buffer.toString();
	}

	private void fireChangeEvent(Object proxy, String name, Object oldValue,
			Object newValue) {
		PropertyChangeEvent event = new PropertyChangeEvent(proxy, name, oldValue, newValue);
		for (PropertyChangeListener each : eventListenerList.getListeners(PropertyChangeListener.class)) {
			each.propertyChange(event);
		}
	}


}
